package com.gitee.pifeng.monitoring.server.business.server.monitor;

import com.gitee.pifeng.monitoring.common.constant.MonitorTypeEnums;
import com.gitee.pifeng.monitoring.common.constant.alarm.AlarmLevelEnums;
import com.gitee.pifeng.monitoring.common.constant.alarm.AlarmReasonEnums;
import com.gitee.pifeng.monitoring.common.domain.Alarm;
import com.gitee.pifeng.monitoring.common.dto.AlarmPackage;
import com.gitee.pifeng.monitoring.common.exception.NetException;
import com.gitee.pifeng.monitoring.common.threadpool.ThreadPoolShutdownHelper;
import com.gitee.pifeng.monitoring.common.util.CollectionUtils;
import com.gitee.pifeng.monitoring.common.util.Md5Utils;
import com.gitee.pifeng.monitoring.common.util.server.ProcessorsUtils;
import com.gitee.pifeng.monitoring.server.business.server.core.MonitoringConfigPropertiesLoader;
import com.gitee.pifeng.monitoring.server.business.server.core.ServerPackageConstructor;
import com.gitee.pifeng.monitoring.server.business.server.entity.TLinkStat;
import com.gitee.pifeng.monitoring.server.business.server.service.IAlarmService;
import com.gitee.pifeng.monitoring.server.business.server.service.ILinkStatService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;


import javax.annotation.PreDestroy;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Author wt
 * @Date: 2023/11/23 13:49
 * @Description:
 */
@Slf4j
@Component
@Order(9)
public class LinkMonitorJob extends QuartzJobBean {

    /**
     * 监控配置属性加载器
     */
    @Autowired
    private MonitoringConfigPropertiesLoader monitoringConfigPropertiesLoader;

    /**
     * 服务端包构造器
     */
    @Autowired
    private ServerPackageConstructor serverPackageConstructor;

    /**
     * 告警服务接口
     */
    @Autowired
    private IAlarmService alarmService;

    /**
     * 数链明细表服务接口
     */
    @Autowired
    private ILinkStatService linkStatService;

    /**
     * 线程池
     */
    private final ScheduledExecutorService seService = new ScheduledThreadPoolExecutor(
            // 线程数 = Ncpu /（1 - 阻塞系数），IO密集型阻塞系数相对较大
            (int) (ProcessorsUtils.getAvailableProcessors() / (1 - 0.8)),
            new BasicThreadFactory.Builder()
                    // 设置线程名
                    .namingPattern("phoenix-server-link-monitor-pool-thread-%d")
                    // 设置为守护线程
                    .daemon(true)
                    .build(),
            new ThreadPoolExecutor.AbortPolicy());

    /**
     * 在程序关闭前优雅关闭线程池
     *
     * @author wt
     * @custom.date 2023/11/23 22:00
     */
    @PreDestroy
    public void shutdownThreadPoolGracefully() {
        new ThreadPoolShutdownHelper().shutdownGracefully(this.seService, "phoenix-server-link-monitor-pool-thread");
    }

    /**
     * <p>
     * 扫描数据库“t_link_stat”表中所有数链告警信息，发送告警。
     * </p>
     *
     * @param jobExecutionContext 作业执行上下文
     * @author wt
     * @custom.date 2023/11/23 10:49
     */
    @Override
    protected void executeInternal(@NonNull JobExecutionContext jobExecutionContext) {
        // 是否监控数链
        boolean isStatTouchCnt = this.monitoringConfigPropertiesLoader.getMonitoringProperties().getLinkProperties().isStatTouchCnt();
//        log.info("===========是否监控数链==========="+isStatTouchCnt);
        if (!isStatTouchCnt) {
            return;
        }
        synchronized (LinkMonitorJob.class) {
            try {
            // 查询数据库中的所有数链明细信息
//            List<TLinkStat> tLinkStats = this.linkStatService.list();
                TLinkStat tLink =new TLinkStat();
            List<TLinkStat> tLinkStats = this.linkStatService.selectSaAppTagList(tLink);
//                log.info("===========tLinkStats.size==========="+tLinkStats.size());
            // 打乱
            Collections.shuffle(tLinkStats);
                // 按每个list大小为10拆分成多个list
                List<List<TLinkStat>> subTLinkStatLists = CollectionUtils.split(tLinkStats, 10);
                for (List<TLinkStat> subTLinkStats : subTLinkStatLists) {
                    // 使用多线程，加快处理速度
                    this.seService.execute(() -> {
                        for (TLinkStat tLinkStat : subTLinkStats) {
                            try {
                                //是否故障链路
//                                if (tLinkStat.getDealed()==0 && tLinkStat.getDealed() != null){
                                    this.isabnormal(tLinkStat);
//                                }
//                                Integer  LinkTime = this.monitoringConfigPropertiesLoader.getMonitoringProperties().getLinkProperties().getLinkTime();
//                                log.info("===========告警阈值几分钟==========="+LinkTime);
//                                if (LinkTime!=0 && LinkTime != null){
//                                    LocalTime time =LocalTime.parse(String.valueOf(tLinkStat.getLagTime()));
//                                    long minutes = ChronoUnit.MINUTES.between(LocalTime.MIDNIGHT, time);
//                                    log.info("===========minutes==========="+minutes);
//                                    if (minutes>=LinkTime){
//                                        this.isabnormal(tLinkStat);
//                                    }
//                                }
                            }catch (Exception e) {
                                log.error("执行数链告警信息异常！", e);
                            }
                        }
                    });
                }
            }catch (Exception e) {
                log.error("定时扫描数据库“t_link_stat”表中所有数链告警信息异常！", e);
            }
        }
    }

    /**
     * <p>
     * 故障链路，则发送告警
    * @throws SQLException SQL异常
    * @throws NetException 获取数据库信息异常
     * </p>
     */
    private void isabnormal(TLinkStat tLinkStat) throws SQLException, NetException {
//        log.info("========故障链路发送告警内容======="+tLinkStat);
        // 告警级别
        AlarmLevelEnums alarmLevelEnum = this.monitoringConfigPropertiesLoader.getMonitoringProperties().getLinkProperties().getLevelEnum();
        // 发送告警
        this.sendAlarmInfo("@", tLinkStat, alarmLevelEnum, AlarmReasonEnums.NORMAL_2_ABNORMAL);

    }

    /**
     * <p>
     * 发送告警信息
     * </p>
     *
     * @param title           告警标题
     * @param tLinkStat       表信息
     * @param alarmLevelEnum  告警级别
     * @param alarmReasonEnum 告警原因
     * @throws NetException 获取数链异常
     * @author wt
     */
    private void sendAlarmInfo(String title, TLinkStat tLinkStat,  AlarmLevelEnums
            alarmLevelEnum, AlarmReasonEnums alarmReasonEnum)
            throws NetException {
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String[] split = tLinkStat.getAlias().split("-");
        String[] splitStrs = tLinkStat.getSubLinkName().split("->");
        String firstValue="";
        String splits="";
        if (splitStrs != null && splitStrs.length > 0){
             firstValue=splitStrs[0];
            if (firstValue.equals(tLinkStat.getRunningOn())){
                splits=split[0];
            }else {
                splits=split[1];
            }
        }
        title = title+splits+"@"+tLinkStat.getProcessName();
        StringBuilder builder = new StringBuilder();
         builder.append("链路名称：").append(tLinkStat.getShowName())
                .append("<br>进程名：").append(tLinkStat.getProcessName())
                .append("<br>服务器：").append(splits)
                .append("<br>检查时间：").append(dateFormat.format(tLinkStat.getCheckTime()))
                .append("<br>故障信息：").append(tLinkStat.getMessage());
        Alarm alarm = Alarm.builder()
                // 保证code的唯一性
                .code(Md5Utils.encrypt32(tLinkStat.getLinkName()+tLinkStat.getSubLinkName()+tLinkStat.getProcessName()+tLinkStat.getRunningOn()+tLinkStat.getCheckTime()+tLinkStat.getProcessStat()))
                .title(title)
                .msg(builder.toString())
                .alarmLevel(alarmLevelEnum)
                .alarmReason(alarmReasonEnum)
                .monitorType(MonitorTypeEnums.LINK)
                .build();
        //不产生告警
        AlarmPackage alarmPackage = this.serverPackageConstructor.structureAlarmPackage(alarm);
        this.alarmService.dealAlarmPackage(alarmPackage);
       int s= this.linkStatService.updateTLinkStat(tLinkStat);
    }

}
